package ro.grig.downscale;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;

import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.ParseDouble;
import org.supercsv.cellprocessor.ParseInt;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.prefs.CsvPreference;

/**
 * <p>
 * Scopul programului este de a obtine o distributie orara pentru o valaore
 * zilnica propusa.
 * <p>
 * 
 * @author grig
 *
 */
public class RunDownscale {
	private ArrayList<RainRecordTO> hourly = new ArrayList<RainRecordTO>();
	private ArrayList<RainRecordTO> daily = new ArrayList<RainRecordTO>();
	private ArrayList<RainRecordTO> propunere = new ArrayList<RainRecordTO>();
	final CellProcessor[] processors = new CellProcessor[] {
			new NotNull(new ParseInt()), new NotNull(new ParseInt()),
			new NotNull(new ParseInt()), new NotNull(new ParseInt()),
			new Optional(new ParseDouble()) };
	String[] nameMapping = new String[] { "year", "month", "day", "hour",
			"rain" };

	/**
	 * <ol>
	 * <li>Citeste valorile orare</li>
	 * <li>Contruieste valorile zilnice</li>
	 * <li>Descompune valorile zilnice obtinute</li>
	 * </ol>
	 * 
	 * @param file
	 * @return
	 */
	public ArrayList<RainRecordTO> readHourlyRain(String file) {
		hourly.clear();
		CsvPreference preferences = CsvPreference.EXCEL_NORTH_EUROPE_PREFERENCE;
		Reader reader;
		try {
			reader = new FileReader(file);
			CsvBeanReader csvReader = new CsvBeanReader(reader, preferences);
			String[] header = csvReader.getHeader(true);
			RainRecordTO read = null;
			int pozitia = 0;
			while ((read = csvReader.read(RainRecordTO.class, nameMapping,
					processors)) != null) {
				read.setPozition(pozitia++);
				hourly.add(read);
			}
			csvReader.close();
			pozitia = 0;
			for (RainRecordTO hourrec : hourly) {
				RainRecordTO dayRec = findRecByDay(daily, hourrec);
				if (dayRec != null) {
					dayRec.add(hourrec);
				} else {
					dayRec = new RainRecordTO();
					dayRec.initForDay(hourrec);
					dayRec.setPozition(pozitia++);
					dayRec.setRain(hourrec.getRain());
					daily.add(dayRec);
				}
			}
			// writeOut(daily);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return hourly;
	}

	private void writeOut(ArrayList<RainRecordTO> daily2) {
		for (RainRecordTO rainRecordTO : daily2) {
			System.out.println(rainRecordTO.getCount() + ","
					+ rainRecordTO.pozition + ",dif="
					+ rainRecordTO.getDiferenta() + " "
					+ rainRecordTO.toString() + "");
		}
	}

	private RainRecordTO findRecByDay(ArrayList<RainRecordTO> daily2,
			RainRecordTO hourrec) {
		RainRecordTO retval = null;
		for (RainRecordTO or : daily2) {
			if (new EqualsBuilder().append(or.getYear(), hourrec.getYear())
					.append(or.getMonth(), hourrec.getMonth())
					.append(or.getDay(), hourrec.getDay()).isEquals()) {
				return or;
			}
		}
		return retval;
	}

	public void doDownscaling(double Y_t) {
		/*
		 * Fac estimarea doar pentru o valoare propusa: luam indicele t in Y_t
		 * si pentru valoarea propusa 1
		 */
		int t = 1;
		//double Y_t = Parametri.Y_t[t];
		/*
		 * Pentru inceput propunem ca n sa fie toate zilele dar se poate
		 * restrange acest lucru in functi de anotimp luna sau alta valoare
		 * specificata.
		 */
		int n = daily.size();
		/*
		 * k este numarul de valori pentru selectie din interval , valorea
		 * preferta este radical din numarul total de valori analizate
		 */
		int k = (int) Math.sqrt(n);
		System.out.println("K este :" + k + " Y_t este " + Y_t);
		double D[] = new double[n];
		for (int i = 0; i < n; i++) {
			RainRecordTO rr = daily.get(i);
			rr.diferenta = Math.pow((Y_t - rr.getRain()), 2);
		}
		/*
		 * Ordoneaza dupa diferenta crescator
		 */
		Collections.sort(daily, new Comparator<RainRecordTO>() {
			@Override
			public int compare(RainRecordTO o1, RainRecordTO o2) {
				return new CompareToBuilder().append(o1.getDiferenta(),
						o2.getDiferenta()).toComparison();
			}
		});
		/*
		 * Alege primele k valori pentru indexi
		 */
		ArrayList<RainRecordTO> selectieK = new ArrayList<RainRecordTO>();
		for (int i = 0; i < k; i++) {
			selectieK.add(daily.get(i));
		}
		writeOut(selectieK);
		/*
		 * Random select un k
		 */
		Random random = new Random(2000);
		int random_k = random.nextInt(k);
		RainRecordTO kr = selectieK.get(random_k);
		ArrayList<RainRecordTO> X_p_K_valoriorare = new ArrayList<RainRecordTO>();
		for (RainRecordTO hr : hourly) {
			if (new EqualsBuilder().append(hr.getYear(), kr.getYear())
					.append(hr.getMonth(), kr.getMonth())
					.append(hr.getDay(), kr.getDay()).isEquals()) {
				X_p_K_valoriorare.add(hr);
			}
		}
		ArrayList<RainRecordTO> valori_propuse = X_p_K_valoriorare;
		/*
		 * Cross over
		 */
		double Pc = 0.4d;
		if (random.nextDouble() < Pc) {
			int random_k1 = random.nextInt(k);
			RainRecordTO k1r = selectieK.get(random_k1);
			while (random_k1 == random_k) {
				random_k1 = random.nextInt(k);
			}
			ArrayList<RainRecordTO> X_p_K1_valoriorare = new ArrayList<RainRecordTO>();
			for (RainRecordTO hr : hourly) {
				if (new EqualsBuilder().append(hr.getYear(), k1r.getYear())
						.append(hr.getMonth(), k1r.getMonth())
						.append(hr.getDay(), k1r.getDay()).isEquals()) {
					X_p_K1_valoriorare.add(hr);
				}
			}
			valori_propuse = X_p_K1_valoriorare;
		}
		/*
		 * Mutatiii
		 */
		double Pm = 0.4d;
		int n_random_select = random.nextInt(k);
		RainRecordTO d_n_mutatie = selectieK.get(n_random_select);
		ArrayList<RainRecordTO> h_n_mutatie = new ArrayList<RainRecordTO>();
		for (RainRecordTO hr : hourly) {
			if (new EqualsBuilder().append(hr.getYear(), d_n_mutatie.getYear())
					.append(hr.getMonth(), d_n_mutatie.getMonth())
					.append(hr.getDay(), d_n_mutatie.getDay()).isEquals()) {
				h_n_mutatie.add(hr);
			}
		}
		for (int nm = 0; nm < valori_propuse.size(); nm++) {
			if (random.nextDouble() < Pm) {
				valori_propuse.set(nm, h_n_mutatie.get(nm));
			}
		}
		/*
		 * Ponderare
		 */
		RainRecordTO suma = new RainRecordTO();
		for (RainRecordTO vp : valori_propuse) {
			suma.add(vp);
		}
		double pondere = Y_t / suma.getRain();
		for (RainRecordTO vp : valori_propuse) {
			vp.setRain(vp.getRain() * pondere);
		}
		/*
		 * Final verificare
		 */
		RainRecordTO sumaYt = new RainRecordTO();
		for (RainRecordTO vp : valori_propuse) {
			sumaYt.add(vp);
		}
		System.out.println("Suma Y_t, verificare:" + Y_t + " vs=" + sumaYt);
		writeOut(valori_propuse);
		propunere = valori_propuse;
	}

	public ArrayList<RainRecordTO> getPropunere() {
		return propunere;
	}

	public void setPropunere(ArrayList<RainRecordTO> propunere) {
		this.propunere = propunere;
	}

	public void writeResult(String outFile) throws Exception {
		FileWriter fr = new FileWriter(outFile);
		CsvPreference preferences = CsvPreference.EXCEL_NORTH_EUROPE_PREFERENCE;
		CsvBeanWriter cvb = new CsvBeanWriter(fr, preferences);
		for (RainRecordTO rr : propunere) {
			cvb.write(rr, nameMapping);
		}
		cvb.flush();
		cvb.close();
	}
}
